#include "copyfile.h"
#include <QCryptographicHash>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QtDebug>
#include <QThread>

namespace yc {

volatile qint64 CopyFileWorker::calc_id_speed = 1024 * 1024;
volatile qint64 CopyFileWorker::copy_speed = 1024 * 1024;

class CopyFilePrivate
{
public:
    QThread thread;
    CopyFileWorker* worker = nullptr;
};

CopyFile::CopyFile(QObject* parent) : QObject(parent), ptr_(new CopyFilePrivate)
{
    ptr_->worker = new CopyFileWorker;
    ptr_->worker->moveToThread(&ptr_->thread);
    connect(this, SIGNAL(operate(QString, QString, QString)), ptr_->worker,
            SLOT(doWork(QString, QString, QString)));
    connect(ptr_->worker, SIGNAL(finished(QString, QString)), this,
            SIGNAL(finished(QString, QString)));
    connect(&ptr_->thread, &QThread::finished, ptr_->worker, &QObject::deleteLater);
}

CopyFile::~CopyFile()
{
    ptr_->thread.quit();
    ptr_->thread.wait();
    delete ptr_;
}

void CopyFile::copy(const QString& file_name, const QString& target_dir,
                    const QString& new_file_name)
{
    if (!ptr_->thread.isRunning()) {
        ptr_->thread.start();
    }
    Q_EMIT operate(file_name, target_dir, new_file_name);
}

CopyFileWorker::CopyFileWorker(QObject* parent) : QObject(parent) { }

QString CopyFileWorker::calcFileID(const QString& file_name, bool multi_times)
{
    QString id;
    QFile file(file_name);
    if (file.open(QIODevice::ReadOnly)) {
        QCryptographicHash crypt(QCryptographicHash::Md5);
        const int kChunkSize = 1024 * 1024;
        qint64 bytes_read = 0;
        qint64 bytes_sleep = 0;
        QByteArray ba(kChunkSize, 0);
        QDateTime now = QDateTime::currentDateTime();
        qint64 total_bytes_read = 0;
        qint64 elapsed;
        while (!file.atEnd()) {
            bytes_read = file.read(ba.data(), kChunkSize);
            if (bytes_read > 0) {
                crypt.addData(QByteArrayView(ba.data(), int(bytes_read)));
                total_bytes_read += bytes_read;
                if (multi_times) {
                    bytes_sleep += bytes_read;
                    if (bytes_sleep >= 50 * 1024 * 1024) {
                        bytes_sleep = 0;
                        QThread::sleep(0);
                    }
                }
                // stat cost
                elapsed = now.msecsTo(QDateTime::currentDateTime());
                if (elapsed > 0) {
                    calc_id_speed = total_bytes_read / elapsed;
                }
            } else {
                break;
            }
        }
        file.close();
        QByteArray result = crypt.result();
        id = result.toHex();
    }
    return id;
}

bool CopyFileWorker::copy(const QString& file_name, const QString& new_file_name, bool multi_times)
{
    bool result = false;
    QFile source(file_name);
    if (source.open(QIODevice::ReadOnly)) {
        QFile target(new_file_name);
        if (target.open(QIODevice::WriteOnly)) {
            const int kChunkSize = 1024 * 1024;
            qint64 bytes_read = 0;
            qint64 bytes_sleep = 0;
            QByteArray ba(kChunkSize, 0);
            QDateTime now = QDateTime::currentDateTime();
            qint64 total_bytes_read = 0;
            qint64 elapsed;
            result = true;
            while (!source.atEnd()) {
                bytes_read = source.read(ba.data(), kChunkSize);
                if (bytes_read > 0) {
                    if (bytes_read == target.write(ba.data(), bytes_read)) {
                        total_bytes_read += bytes_read;
                        if (multi_times) {
                            bytes_sleep += bytes_read;
                            if (bytes_sleep >= 50 * 1024 * 1024) {
                                bytes_sleep = 0;
                                QThread::sleep(0);
                            }
                        }
                        // stat cost
                        elapsed = now.msecsTo(QDateTime::currentDateTime());
                        if (elapsed > 0) {
                            copy_speed = total_bytes_read / elapsed;
                        }
                    } else {
                        result = false;
                        break;
                    }
                } else {
                    result = false;
                    break;
                }
            }
        }
    }
    return result;
}

QString CopyFileWorker::copyTo(const QString& file_name, const QString& target_dir,
                               const QString& new_file_name)
{
    QString new_name;
    if (!target_dir.isEmpty()) {
        new_name = new_file_name;
        if (new_file_name.isEmpty()) {
            QString id = calcFileID(file_name, true);
            if (!id.isEmpty()) {
                QFileInfo fi(file_name);
                new_name = id + "." + fi.suffix();
            }
        }
        if (!new_name.isEmpty()) {
            QDir dir(target_dir);
            new_name = dir.absoluteFilePath(new_name);
            if (!QFile::exists(new_name)
                || QFileInfo(file_name).size() != QFileInfo(new_name).size()) {
                if (!copy(file_name, new_name, true)) {
                    new_name.clear();
                }
            }
        }
    }
    return new_name;
}

qint64 CopyFileWorker::calcFileIDCost(qint64 size)
{
    return (calc_id_speed > 0 ? size / calc_id_speed : size);
}

qint64 CopyFileWorker::copyFileCost(qint64 size)
{
    return (copy_speed > 0 ? size / copy_speed : size);
}

void CopyFileWorker::doWork(const QString& file_name, const QString& target_dir,
                            const QString& new_file_name)
{
    QString new_name = copyTo(file_name, target_dir, new_file_name);
    Q_EMIT finished(file_name, new_name);
}
} // namespace yc
